<!DOCTYPE html><html>

<head>
<meta charset="utf-8">
<title></title>
<style>
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font:inherit;font-size:100%;vertical-align:baseline}
html{line-height:1}
ol,ul{list-style:none}
table{border-collapse:collapse;border-spacing:0;
  margin-top: 0;
  margin-bottom: 0.8em;
}
caption,th,td{text-align:left;font-weight:normal;vertical-align:middle}
q,blockquote{quotes:none}
q:before,q:after,blockquote:before,blockquote:after{content:"";content:none}
a img{border:none}
article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary{display:block}
a{color:#1863a1}
a:visited{color:#751590}
a:focus{color:#0181eb}
a:hover{color:#0181eb}
a:active{color:#01579f}
aside.sidebar a{color:#222}
aside.sidebar a:focus{color:#0181eb}
aside.sidebar a:hover{color:#0181eb}
aside.sidebar a:active{color:#01579f}
a{-webkit-transition:color 0.3s;-moz-transition:color 0.3s;-o-transition:color 0.3s;transition:color 0.3s}

body,h1,h2,h3,h4,h5,h6,footer{font-family:"PT Sans","Helvetica Neue","Optima","Hiragino Sans GB",sans-serif}
body{line-height:1.5em;color:#222
	-webkit-text-size-adjust:none;  min-width: 200px;
  max-width: 760px;
  margin: 0 auto; padding: 1em;}

pre,code,tt,p code,li code{font-family:Menlo,Monaco,"Andale Mono","lucida console","Courier New",monospace}

h1{font-size:2.2em;line-height:1.2em}

h1,h2,h3,h4,h5,h6{text-rendering:optimizelegibility;margin-bottom:1em;font-weight:bold}
h2,section h1{font-size:1.5em}
h3,section h2,section section h1{font-size:1.3em}
h4,section h3,section section h2,section section section h1{font-size:1em}
h5,section h4,section section h3{font-size:.9em}
h6,section h5,section section h4,section section section h3{font-size:.8em}
.markdown-body{padding: 0px 4px;}
.markdown-body h1{position:relative;padding-top:1em;padding-bottom:0.2em;margin-bottom:1em;background:url('') bottom left repeat-x}
.markdown-body  h1 a{text-decoration:none}
.markdown-body  h1 a:hover{text-decoration:underline}

.markdown-body h2{padding-top:0.8em;padding-bottom:0.2em;background:url('') bottom left repeat-x}
.markdown-body h2:first-child,.markdown-body header+h2{padding-top:0}
.markdown-body h2:first-child,.markdown-body header+h2{background:none}



p,.markdown-body blockquote,ul,ol{margin-bottom:0.8em; margin-top:0.8em;}
ul{list-style-type:disc}
ul ul{list-style-type:circle;margin-bottom:0px}
ul ul ul{list-style-type:square;margin-bottom:0px}
ol{list-style-type:decimal}
ol ol{list-style-type:lower-alpha;margin-bottom:0px}
ol ol ol{list-style-type:lower-roman;margin-bottom:0px}
ul,ul ul,ul ol,ol,ol ul,ol ol{margin-left:1.3em}
ul ul,ul ol,ol ul,ol ol{margin-bottom:0em}
strong{font-weight:bold}
em{font-style:italic}
sup,sub{font-size:0.75em;position:relative;display:inline-block;padding:0 .2em;line-height:.8em}
sup{top:-.5em}
sub{bottom:-.5em}
a[rev='footnote']{font-size:.75em;padding:0 .3em;line-height:1}
q{font-style:italic}
q:before{content:"\201C"}
q:after{content:"\201D"}
em,dfn{font-style:italic}
strong,dfn{font-weight:bold}
del,s{text-decoration:line-through}
abbr,acronym{border-bottom:1px dotted;cursor:help}
small{font-size:.8em}
big{font-size:1.2em}

.markdown-body hr {
  height: 0;
  margin: 15px 0;
  overflow: hidden;
  background: transparent;
  border: 0;
  border-bottom: 1px solid #ddd;
}

.markdown-body hr:before {
  display: table;
  content: "";
}

.markdown-body hr:after {
  display: table;
  clear: both;
  content: "";
}


.markdown-body table {
  display: block;
  width: 100%;
  overflow: auto;
  word-break: normal;
  word-break: keep-all;
}

.markdown-body table th {
  font-weight: bold;
}

.markdown-body table th,
.markdown-body table td {
  padding: 6px 13px;
  border: 1px solid #ddd;
}

.markdown-body table tr {
  background-color: #fff;
  border-top: 1px solid #ccc;
}

.markdown-body table tr:nth-child(2n) {
  background-color: #f8f8f8;
}

.markdown-body blockquote{font-style:italic;position:relative;font-size:1.2em;line-height:1.5em;padding-left:1em;border-left:4px solid rgba(170,170,170,0.5)}
.markdown-body blockquote cite{font-style:italic}
.markdown-body blockquote cite a{color:#aaa !important;word-wrap:break-word}
.markdown-body blockquote cite:before{content:'\2014';padding-right:.3em;padding-left:.3em;color:#aaa}

.markdown-body a{white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;white-space:pre-wrap;word-wrap:break-word}

body>header{font-size:1em;padding-top:1.5em;padding-bottom:1.5em}
.markdown-body{overflow:hidden}
.markdown-body>div,.markdown-body>article{width:100%}
aside.sidebar{float:none;padding:0 18px 1px;background-color:#f7f7f7;border-top:1px solid #e0e0e0}
.flex-content,article img,article video,article .flash-video,aside.sidebar img{max-width:100%;height:auto}
.basic-alignment.left,article img.left,article video.left,article .left.flash-video,aside.sidebar img.left{float:left;margin-right:1.5em}
.basic-alignment.right,article img.right,article video.right,article .right.flash-video,aside.sidebar img.right{float:right;margin-left:1.5em}
.basic-alignment.center,article img.center,article video.center,article .center.flash-video,aside.sidebar img.center{display:block;margin:0 auto 1.5em}
.basic-alignment.left,article img.left,article video.left,article .left.flash-video,aside.sidebar img.left,.basic-alignment.right,article img.right,article video.right,article .right.flash-video,aside.sidebar img.right{margin-bottom:.8em}
.toggle-sidebar,.no-sidebar .toggle-sidebar{display:none}


.markdown-body img,.markdown-body video,.markdown-body .flash-video{ -webkit-border-radius:0.3em;-moz-border-radius:0.3em;-ms-border-radius:0.3em;-o-border-radius:0.3em;border-radius:0.3em;-webkit-box-shadow:rgba(0,0,0,0.15) 0 1px 4px;-moz-box-shadow:rgba(0,0,0,0.15) 0 1px 4px;box-shadow:rgba(0,0,0,0.15) 0 1px 4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border:#fff 0.5em solid;}

.markdown-body img,.markdown-body video{max-width: 100%;}
.markdown-body video,.markdown-body .flash-video{margin:0 auto 1.5em}
.markdown-body video{display:block;width:100%}
.markdown-body .flash-video>div{position:relative;display:block;padding-bottom:56.25%;padding-top:1px;height:0;overflow:hidden}
.markdown-body .flash-video>div iframe,.markdown-body .flash-video>div object,.markdown-body .flash-video>div embed{position:absolute;top:0;left:0;width:100%;height:100%}
.markdown-body>footer{padding-bottom:2.5em;margin-top:2em}
.markdown-body>footer p.meta{margin-bottom:.8em;font-size:.85em;clear:both;overflow:hidden}


body,pre{ background:#fdf6e3 url('') top left;}
body{ background-color: #f8f8f8;}
pre{-webkit-border-radius:0.4em;-moz-border-radius:0.4em;-ms-border-radius:0.4em;-o-border-radius:0.4em;border-radius:0.4em;border:1px solid #e7dec3;line-height:1.45em;font-size:13px;margin-bottom:2.1em;padding:.8em 1em;color:#586e75;overflow:auto}
.markdown-body code{background: none;}
h3.filename+pre{-moz-border-radius-topleft:0px;-webkit-border-top-left-radius:0px;border-top-left-radius:0px;-moz-border-radius-topright:0px;-webkit-border-top-right-radius:0px;border-top-right-radius:0px}
p code,li code{display:inline-block;white-space:no-wrap;background:#fff;font-size:.8em;line-height:1.5em;color:#555;border:1px solid #ddd;-webkit-border-radius:0.4em;-moz-border-radius:0.4em;-ms-border-radius:0.4em;-o-border-radius:0.4em;border-radius:0.4em;padding:0 .3em;margin:-1px 0}
p pre code,li pre code{font-size:1em !important;background:none;border:none}

/*

Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull <sourdrums@gmail.com>

*/

.hljs {
  display: block;
  overflow-x: auto;
  padding: 0.5em;
  background: #fdf6e3;
  color: #657b83;
  -webkit-text-size-adjust: none;
}

.hljs-comment,
.diff .hljs-header,
.hljs-doctype,
.hljs-pi,
.lisp .hljs-string {
  color: #93a1a1;
}

/* Solarized Green */
.hljs-keyword,
.hljs-winutils,
.method,
.hljs-addition,
.css .hljs-tag,
.hljs-request,
.hljs-status,
.nginx .hljs-title {
  color: #859900;
}

/* Solarized Cyan */
.hljs-number,
.hljs-command,
.hljs-string,
.hljs-tag .hljs-value,
.hljs-rule .hljs-value,
.hljs-doctag,
.tex .hljs-formula,
.hljs-regexp,
.hljs-hexcolor,
.hljs-link_url {
  color: #2aa198;
}

/* Solarized Blue */
.hljs-title,
.hljs-localvars,
.hljs-chunk,
.hljs-decorator,
.hljs-built_in,
.hljs-identifier,
.vhdl .hljs-literal,
.hljs-id,
.css .hljs-function,
.hljs-name {
  color: #268bd2;
}

/* Solarized Yellow */
.hljs-attribute,
.hljs-variable,
.lisp .hljs-body,
.smalltalk .hljs-number,
.hljs-constant,
.hljs-class .hljs-title,
.hljs-parent,
.hljs-type,
.hljs-link_reference {
  color: #b58900;
}

/* Solarized Orange */
.hljs-preprocessor,
.hljs-preprocessor .hljs-keyword,
.hljs-pragma,
.hljs-shebang,
.hljs-symbol,
.hljs-symbol .hljs-string,
.diff .hljs-change,
.hljs-special,
.hljs-attr_selector,
.hljs-subst,
.hljs-cdata,
.css .hljs-pseudo,
.hljs-header {
  color: #cb4b16;
}

/* Solarized Red */
.hljs-deletion,
.hljs-important {
  color: #dc322f;
}

/* Solarized Violet */
.hljs-link_label {
  color: #6c71c4;
}

.tex .hljs-formula {
  background: #eee8d5;
}


</style>

<style> @media print{ .hljs{overflow: visible; word-wrap: break-word !important;} }</style></head><body><div class="markdown-body">
<h1 id="toc_0">Converting A PyTorch Model to Tensorflow pb using ONNX</h1>

<p align="right">pilgrim.bin@gmail.com</p>

<ul>
<li>
<a href="#toc_0">Converting A PyTorch Model to Tensorflow pb using ONNX</a>
</li>
<li>
<a href="#toc_1">1. Pre-installation</a>
</li>
<li>
<a href="#toc_2">2. 转换过程</a>
<ul>
<li>
<a href="#toc_3">2.1 Step 1.2.3.</a>
</li>
<li>
<a href="#toc_4">2.2 Verification</a>
</li>
</ul>
</li>
<li>
<a href="#toc_5">3. Related Info</a>
<ul>
<li>
<a href="#toc_6">3.1 ONNX</a>
</li>
<li>
<a href="#toc_7">3.2 Microsoft/MMdnn</a>
</li>
</ul>
</li>
<li>
<a href="#toc_8">Reference</a>
</li>
</ul>


<h1 id="toc_1">1. Pre-installation</h1>

<p><strong>Version Info</strong></p>

<pre><code>pytorch                   0.4.0           py27_cuda0.0_cudnn0.0_1    pytorch
torchvision               0.2.1                    py27_1    pytorch
tensorflow                1.8.0                     &lt;pip&gt;
onnx                      1.2.2                     &lt;pip&gt;
onnx-tf                   1.1.2                     &lt;pip&gt; 
</code></pre>

<p>注意：</p>

<ol>
<li>ONNX1.1.2版本太低会引发BatchNormalization错误，当前pip已经支持1.3.0版本；也可以考虑源码安装 <code>pip install -U git+https://github.com/onnx/onnx.git@master</code>。</li>
<li>本实验验证ONNX1.2.2版本可正常运行</li>
<li>onnx-tf采用源码安装；要求 Tensorflow&gt;=1.5.0.；</li>
</ol>

<h1 id="toc_2">2. 转换过程</h1>

<h2 id="toc_3">2.1 Step 1.2.3.</h2>

<p><strong>pipeline: pytorch model --&gt; onnx modle --&gt; tensorflow graph pb.</strong></p>

<pre><code># step 1, load pytorch model and export onnx during running.
    modelname = &#39;resnet18&#39;
    weightfile = &#39;models/model_best_checkpoint_resnet18.pth.tar&#39;
    modelhandle = DIY_Model(modelname, weightfile, class_numbers)
    model = modelhandle.model
    #model.eval() # useless
    dummy_input = Variable(torch.randn(1, 3, 224, 224)) # nchw
    onnx_filename = os.path.split(weightfile)[-1] + &quot;.onnx&quot;
    torch.onnx.export(model, dummy_input,
                      onnx_filename,
                      verbose=True)
    
    # step 2, create onnx_model using tensorflow as backend. check if right and export graph.
    onnx_model = onnx.load(onnx_filename)
    tf_rep = prepare(onnx_model, strict=False)
    # install onnx-tensorflow from github，and tf_rep = prepare(onnx_model, strict=False)
    # Reference https://github.com/onnx/onnx-tensorflow/issues/167
    #tf_rep = prepare(onnx_model) # whthout strict=False leads to KeyError: &#39;pyfunc_0&#39;
    image = Image.open(&#39;pants.jpg&#39;)
    # debug, here using the same input to check onnx and tf.
    output_pytorch, img_np = modelhandle.process(image)
    print(&#39;output_pytorch = {}&#39;.format(output_pytorch))
    output_onnx_tf = tf_rep.run(img_np)
    print(&#39;output_onnx_tf = {}&#39;.format(output_onnx_tf))
    # onnx --&gt; tf.graph.pb
    tf_pb_path = onnx_filename + &#39;_graph.pb&#39;
    tf_rep.export_graph(tf_pb_path)
    
    # step 3, check if tf.pb is right.
    with tf.Graph().as_default():
        graph_def = tf.GraphDef()
        with open(tf_pb_path, &quot;rb&quot;) as f:
            graph_def.ParseFromString(f.read())
            tf.import_graph_def(graph_def, name=&quot;&quot;)
        with tf.Session() as sess:
            #init = tf.initialize_all_variables()
            init = tf.global_variables_initializer()
            #sess.run(init)
            
            # print all ops, check input/output tensor name.
            # uncomment it if you donnot know io tensor names.
            &#39;&#39;&#39;
            print(&#39;-------------ops---------------------&#39;)
            op = sess.graph.get_operations()
            for m in op:
                print(m.values())
            print(&#39;-------------ops done.---------------------&#39;)
            &#39;&#39;&#39;

            input_x = sess.graph.get_tensor_by_name(&quot;0:0&quot;) # input
            outputs1 = sess.graph.get_tensor_by_name(&#39;add_1:0&#39;) # 5
            outputs2 = sess.graph.get_tensor_by_name(&#39;add_3:0&#39;) # 10
            output_tf_pb = sess.run([outputs1, outputs2], feed_dict={input_x:img_np})
            #output_tf_pb = sess.run([outputs1, outputs2], feed_dict={input_x:np.random.randn(1, 3, 224, 224)})
            print(&#39;output_tf_pb = {}&#39;.format(output_tf_pb))
</code></pre>

<h2 id="toc_4">2.2 Verification</h2>

<p><strong>确保输出结果一致</strong></p>

<pre><code>output_pytorch = [array([ 2.5359073 , -1.4261041 , -5.2394    , -0.62402934,  4.7426634 ], dtype=float32), array([ 7.6249304,  5.1203837,  1.8118637,  1.5143847, -4.9409146, 1.1695148, -6.2375665, -1.6033885, -1.4286405, -2.964429 ], dtype=float32)]
      
output_onnx_tf = Outputs(_0=array([[ 2.5359051, -1.4261056, -5.239397 , -0.6240269,  4.7426634]], dtype=float32), _1=array([[ 7.6249285,  5.12038  ,  1.811865 ,  1.5143874, -4.940915 , 1.1695154, -6.237564 , -1.6033876, -1.4286422, -2.964428 ]], dtype=float32))
      
output_tf_pb = [array([[ 2.5359051, -1.4261056, -5.239397 , -0.6240269,  4.7426634]], dtype=float32), array([[ 7.6249285,  5.12038  ,  1.811865 ,  1.5143874, -4.940915 , 1.1695154, -6.237564 , -1.6033876, -1.4286422, -2.964428 ]], dtype=float32)]
</code></pre>

<p><strong>独立TF验证程序</strong></p>

<pre><code>def get_img_np_nchw(filename):
    try:
        image = Image.open(filename).convert(&#39;RGB&#39;).resize((224, 224))
        miu = np.array([0.485, 0.456, 0.406])
        std = np.array([0.229, 0.224, 0.225])
        #miu = np.array([0.5, 0.5, 0.5])
        #std = np.array([0.22, 0.22, 0.22])
        # img_np.shape = (224, 224, 3)
        img_np = np.array(image, dtype=float) / 255.
        r = (img_np[:,:,0] - miu[0]) / std[0]
        g = (img_np[:,:,1] - miu[1]) / std[1]
        b = (img_np[:,:,2] - miu[2]) / std[2]
        img_np_t = np.array([r,g,b])
        img_np_nchw = np.expand_dims(img_np_t, axis=0)
        return img_np_nchw
    except:
        print(&quot;RuntimeError: get_img_np_nchw({}).&quot;.format(filename))
        # NoneType
    

if __name__ == &#39;__main__&#39;:
    
    tf_pb_path = &#39;model_best_checkpoint_resnet18.pth.tar.onnx_graph.pb&#39;
    
    filename = &#39;pants.jpg&#39;
    img_np_nchw = get_img_np_nchw(filename)
    
    # step 3, check if tf.pb is right.
    with tf.Graph().as_default():
        graph_def = tf.GraphDef()
        with open(tf_pb_path, &quot;rb&quot;) as f:
            graph_def.ParseFromString(f.read())
            tf.import_graph_def(graph_def, name=&quot;&quot;)
        with tf.Session() as sess:
            init = tf.global_variables_initializer()
            #init = tf.initialize_all_variables()
            sess.run(init)
            
            # print all ops, check input/output tensor name.
            # uncomment it if you donnot know io tensor names.
            &#39;&#39;&#39;
            print(&#39;-------------ops---------------------&#39;)
            op = sess.graph.get_operations()
            for m in op:
                print(m.values())
            print(&#39;-------------ops done.---------------------&#39;)
            &#39;&#39;&#39;

            input_x = sess.graph.get_tensor_by_name(&quot;0:0&quot;) # input
            outputs1 = sess.graph.get_tensor_by_name(&#39;add_1:0&#39;) # 5
            outputs2 = sess.graph.get_tensor_by_name(&#39;add_3:0&#39;) # 10
            output_tf_pb = sess.run([outputs1, outputs2], feed_dict={input_x:img_np_nchw})
            print(&#39;output_tf_pb = {}&#39;.format(output_tf_pb))
</code></pre>

<h1 id="toc_5">3. Related Info</h1>

<h2 id="toc_6">3.1 ONNX</h2>

<p>Open Neural Network Exchange<br/>
<a href="https://github.com/onnx">https://github.com/onnx</a><br/>
<a href="https://onnx.ai/">https://onnx.ai/</a></p>

<p>The ONNX exporter is a <mark><strong>trace-based</strong></mark> exporter, which means that it operates by executing your model once, and exporting the operators which were actually run during this run. <a href="https://pytorch.org/docs/stable/onnx.html#example-end-to-end-alexnet-from-pytorch-to-caffe2">Limitations</a></p>

<p><a href="https://github.com/onnx/tensorflow-onnx">https://github.com/onnx/tensorflow-onnx</a><br/>
<a href="https://github.com/onnx/onnx-tensorflow">https://github.com/onnx/onnx-tensorflow</a></p>

<h2 id="toc_7">3.2 Microsoft/MMdnn</h2>

<p>当前网络没有调通<br/>
<a href="https://github.com/Microsoft/MMdnn/blob/master/mmdnn/conversion/pytorch/README.md">https://github.com/Microsoft/MMdnn/blob/master/mmdnn/conversion/pytorch/README.md</a></p>

<h1 id="toc_8">Reference</h1>

<ol>
<li>Open Neural Network Exchange <a href="https://github.com/onnx">https://github.com/onnx</a></li>
<li><a href="https://github.com/onnx/tutorials/blob/master/tutorials/PytorchOnnxExport.ipynb">Exporting model from PyTorch to ONNX</a></li>
<li><a href="https://github.com/onnx/tutorials/blob/master/tutorials/OnnxTensorflowImport.ipynb">Importing ONNX models to Tensorflow(ONNX)</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/26136080">Tensorflow + tornado服务</a></li>
<li><a href="https://github.com/llSourcell/tensorflow_image_classifier/blob/master/src/label_image.py">graph_def = tf.GraphDef() graph_def.ParseFromString(f.read())</a></li>
<li><a href="https://www.tensorflow.org/extend/tool_developers/">A Tool Developer&#39;s Guide to TensorFlow Model Files</a></li>
<li><a href="https://www.jianshu.com/p/613c3b08faea">TensorFlow学习笔记：Retrain Inception_v3</a></li>
</ol>

</div></body>

</html>
